package com.hay.pay.support.wechat.partner;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.hay.pay.enums.PayChannelEnum;
import com.hay.pay.enums.RefundStatusEnum;
import com.hay.pay.model.PayConfig;
import com.hay.pay.model.param.*;
import com.hay.pay.model.result.*;
import com.hay.pay.support.PayContext;
import com.hay.pay.support.PayPlugin;
import com.hay.pay.support.wechat.WechatApiEnum;
import com.hay.pay.support.wechat.WechatPayConfig;
import com.hay.pay.support.wechat.WechatPayRequest;
import com.hay.pay.support.wechat.WechatPaySign;
import com.hay.pay.support.wechat.enums.WechatRefundStatusEnum;
import com.hay.pay.support.wechat.enums.WechatTradeStatusEnum;
import com.hay.pay.support.wechat.param.WechatJsApiPlaceOrderParam;
import com.hay.pay.util.DateUtil;
import com.hay.pay.util.WebUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

/**
 * 微信-合作伙伴-JSAPI 支付插件
 * @author LiMouRen
 * @date 2024/1/24
 */
@Slf4j
public class WechatPartnerJsApiPayPlugin implements PayPlugin {

    public static final PayChannelEnum PAY_CHANNEL = PayChannelEnum.WECHAT_PARTNER_JSAPI;

    /**
      下单
     * <a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_1.shtml"/>详见</a>
     * @param placeOrderParam 下单参数
     * @return 下单结果
     */
    @Override
    public PlaceOrderResult placeOrder(PlaceOrderParam placeOrderParam) {
        WechatPayConfig payConfig = getWechatPayConfig(placeOrderParam.getMerchantCode());
        WechatJsApiPlaceOrderParam param = placeOrderParam.getSceneParams(WechatJsApiPlaceOrderParam.class);
        // 构建请求
        WechatPayRequest request = WechatPayRequest.initPost()
                .payConfig(payConfig)
                .build();
        // 构建请求参数
        ObjectNode rootNode = new ObjectMapper().createObjectNode();
        rootNode.put("sp_appid", param.getAppId())
                .put("sp_mchid", payConfig.getMchId())
                .put("sub_mchid", payConfig.getSubMchId())
                .put("out_trade_no", param.getOutTradeNo())
                .put("description", param.getDescription())
                .put("notify_url", param.getNotifyUrl());
        rootNode.putObject("amount")
                .put("total", param.getTotalAmount())
                .put("currency", "CNY");
        rootNode.putObject("payer")
                .put("sp_openid", param.getOpenId());
        // 发起请求
        JSONObject responseBody = request.execute(WechatApiEnum. PARTNER_JS_API_PAY.getUrl(), rootNode);
        String prepayId = responseBody.getString("prepay_id");
        Assert.hasText(prepayId,responseBody.getString("message"));
        PlaceOrderResult placeOrderResult = new PlaceOrderResult();
        placeOrderResult.setResult(responseBody.toJSONString());
        placeOrderResult.setThirdTransactionCode(prepayId);
        placeOrderResult.setOrderCode(param.getOutTradeNo());
        placeOrderResult.setNotifyUrl(param.getNotifyUrl());
        return placeOrderResult;
    }

    /**
     * 关闭订单
     * <a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_3.shtml"/>详见</a>
     * @param closeOrderParam 关闭订单参数
     */
    @Override
    public void closeOrder(CloseOrderParam closeOrderParam) {
        WechatPayConfig payConfig = getWechatPayConfig(closeOrderParam.getMerchantCode());
        // 构建请求
        WechatPayRequest request = WechatPayRequest.initPost()
                .payConfig(payConfig)
                .build();
        ObjectNode rootNode = new ObjectMapper().createObjectNode();
        rootNode.put("sp_mchid", payConfig.getMchId());
        rootNode.put("sub_mchid",payConfig.getSubMchId());
        // 发起请求
        String apiUrl = WechatApiEnum.PARTNER_CLOSE_ORDER_BY_NO.formatUrl(closeOrderParam.getOrderCode());
        request.execute(apiUrl, rootNode);
    }

    /**
     * 查询下单结果
     * <a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_2.shtmll"/>详见</a>
     * @param searchOrderParam 查询订单参数
     *  @return 下单结果
     */
    @Override
    public PayNotifyResult searchPlaceOrder(SearchPlaceOrderParam searchOrderParam) {
        WechatPayConfig payConfig = getWechatPayConfig(searchOrderParam.getMerchantCode());
        // 构建请求
        WechatPayRequest request = WechatPayRequest.initGet()
                .payConfig(payConfig)
                .build();
        // 构建请求参数
        String mchId = payConfig.getMchId();
        String subMchId = payConfig.getSubMchId();
        String apiUrl = WechatApiEnum.PARTNER_ORDER_QUERY_BY_NO.formatUrl(searchOrderParam.getOrderCode(),mchId,subMchId);
        // 发起请求
        JSONObject responseBody = request.execute(apiUrl, null);
        return convertSearchPlaceOrderResult(responseBody);
    }

    /**
     * 唤起收银台
     * <a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_4.shtml"/>详见</a>
     * @param arouseCashierParam 参数
     * @return 结果
     */
    @Override
    public JSONObject arouseCashier(ArouseCashierParam arouseCashierParam) {
        WechatPayConfig payConfig = getWechatPayConfig(arouseCashierParam.getMerchantCode());
        String appId = arouseCashierParam.getAppId();
        String prepayId = arouseCashierParam.getThirdTransactionCode();
        String time = System.currentTimeMillis() / 1000 + "";
        String nonceStr = UUID.randomUUID().toString().replace("-", "");
        String packageStr = "prepay_id=" + prepayId;
        List<String> signMessage = new ArrayList<>();
        signMessage.add(appId);
        signMessage.add(time);
        signMessage.add(nonceStr);
        signMessage.add(packageStr);
        String paySign = WechatPaySign.sign(payConfig.getMchCertPrivateKey(),signMessage);
        JSONObject sign = new JSONObject();
        sign.put("appid", appId);
        sign.put("timeStamp", time);
        sign.put("nonceStr", nonceStr);
        sign.put("packages", packageStr);
        sign.put("signType", "RSA");
        sign.put("paySign", paySign);
        return sign;
    }

    /**
     * 申请退款
     * <a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_9.shtml"/>详见</a>
     * @param refundOrderParam 退款参数
     * @return 申请退款结果
     */
    @Override
    public RefundOrderResult refundOrder(RefundOrderParam refundOrderParam) {
        WechatPayConfig payConfig = getWechatPayConfig(refundOrderParam.getMerchantCode());
        // 构建请求
        WechatPayRequest request = WechatPayRequest.initPost()
                .payConfig(payConfig)
                .build();
        // 构建请求参数
        ObjectNode rootNode = new ObjectMapper().createObjectNode();
        rootNode.put("sub_mchid",payConfig.getSubMchId())
                .put("transaction_id", refundOrderParam.getThirdTransactionCode())
                .put("out_refund_no", refundOrderParam.getOrderCode())
                .put("notify_url",refundOrderParam.getNotifyUrl());
        rootNode.putObject("amount")
                .put("refund", refundOrderParam.getRefundAmount())
                .put("total", refundOrderParam.getPayAmount())
                .put("currency","CNY");
        // 发起请求
        JSONObject responseBody = request.execute(WechatApiEnum.DOMESTIC_REFUNDS.getUrl(), rootNode);
        RefundOrderResult refundOrderResult = new RefundOrderResult();
        refundOrderResult.setResult(responseBody.toJSONString());
        refundOrderResult.setThirdRefundCode(responseBody.getString("refund_id"));
        return refundOrderResult;
    }

    /**
     * 查询退款
     * <a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_10.shtml"/>详见</a>
     * @param refundOrderParam 查询参数参数
     * @return 退款结果
     */
    @Override
    public RefundNotifyResult searchRefundOrder(SearchRefundOrderParam refundOrderParam) {
        WechatPayConfig payConfig = getWechatPayConfig(refundOrderParam.getMerchantCode());
        // 构建请求
        WechatPayRequest request = WechatPayRequest.initGet()
                .payConfig(getWechatPayConfig(refundOrderParam.getMerchantCode()))
                .build();
        // 发起请求
        String apiUrl = WechatApiEnum.DOMESTIC_REFUNDS.getUrl()+"/"+refundOrderParam.getOrderCode()+"?sub_mchid="+payConfig.getSubMchId();
        JSONObject responseBody = request.execute(apiUrl, null);
        return convertSearchRefundOrderResult(responseBody);
    }

    /**
     * 支付回调,只有支付成功才有支付回掉
     * <a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_5.shtml"/>详见</a>
     * @param request 请求
     * @param response 响应
     */
    @Override
    public HandlePayNotifyResult handlePayNotify(HttpServletRequest request, HttpServletResponse response, String merchantCode) {
        HandlePayNotifyResult handleResult = new HandlePayNotifyResult();
        boolean notifySuccess = true;
        try {
            // 获取支付配置
            WechatPayConfig wechatPayConfig = getWechatPayConfig(merchantCode);
            // 获取解密数据
            String decryptNotify = WechatPaySign.decryptNotify(request,
                    wechatPayConfig.getPayCertSerialNo(),
                    wechatPayConfig.getPayCertPublicKey(),
                    wechatPayConfig.getApiV3Key());
            // 转换
            PayNotifyResult result = convertPayNotifyResult(decryptNotify);
            handleResult.setResult(result);
            // 通知
            payNotify(result);
        }catch (Exception e){
            log.error("requestHeader:{}", WebUtil.getHeaderParams(request));
            log.error("requestBody: {}", WebUtil.getRequestBody(request));
            log.error("handlePayNotify error: {}",e.getMessage(),e);
            notifySuccess = false;
            handleResult.setErrorMessage(e.getMessage());
        }
        WechatPayRequest.noticeResponse(response,notifySuccess);
        handleResult.setNotifySuccess(notifySuccess);
        return handleResult;
    }

    /**
     * 退款回调
     * <a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_9.shtml">详见</a>
     * @param request 请求
     * @param response 响应
     */
    @Override
    public HandleRefundNotifyResult handleRefundNotify(HttpServletRequest request, HttpServletResponse response,String merchantCode) {
        HandleRefundNotifyResult handleResult = new HandleRefundNotifyResult();
        boolean notifySuccess = true;
        try {
            // 获取支付配置
            WechatPayConfig wechatPayConfig = getWechatPayConfig(merchantCode);
            // 获取解密数据
            String decryptNotify = WechatPaySign.decryptNotify(request,
                    wechatPayConfig.getPayCertSerialNo(),
                    wechatPayConfig.getMchCertPrivateKey(),
                    wechatPayConfig.getApiV3Key());
            // 转换
            RefundNotifyResult result = convertRefundNotifyResult(decryptNotify);
            handleResult.setResult(result);
            refundNotify(result);
        }catch (Exception e){
            log.error("requestHeader:{}", WebUtil.getHeaderParams(request));
            log.error("requestBody: {}", WebUtil.getRequestBody(request));
            log.error("handleRefundNotify error: {}",e.getMessage(),e);
            notifySuccess = false;
            handleResult.setErrorMessage(e.getMessage());
        }
        WechatPayRequest.noticeResponse(response,notifySuccess);
        handleResult.setNotifySuccess(notifySuccess);
        return handleResult;
    }

    private PayNotifyResult convertPayNotifyResult(String decryptNotify) {
        PayNotifyResult payNotifyResult = new PayNotifyResult();
        JSONObject notifyContent = JSON.parseObject(decryptNotify);
        String outTradeNo = notifyContent.getString("out_trade_no");
        String transactionId = notifyContent.getString("transaction_id");
        String tradeState = notifyContent.getString("trade_state");
        String openId = notifyContent.getJSONObject("payer").getString("openid");
        String amountStr = notifyContent.getJSONObject("amount").getString("payer_total");
        payNotifyResult.setResult(decryptNotify);
        payNotifyResult.setOrderCode(outTradeNo);
        payNotifyResult.setPayAmount(Long.valueOf(amountStr));
        payNotifyResult.setThirdPayerCode(openId);
        payNotifyResult.setThirdTransactionCode(transactionId);
        payNotifyResult.setThirdTransactionStatus(tradeState);
        if (WechatTradeStatusEnum.SUCCESS.name().equals(tradeState)){
            String successTimeStr = notifyContent.getString("success_time");
            Date successTime = DateUtil.parse(successTimeStr,"yyyy-MM-dd'T'HH:mm:ss");
            payNotifyResult.setSuccess(true);
            payNotifyResult.setPaySuccessTime(successTime);
        }else {
            payNotifyResult.setSuccess(false);
        }
        return payNotifyResult;
    }

    private RefundNotifyResult convertRefundNotifyResult(String decryptNotify) {
        RefundNotifyResult result = new RefundNotifyResult();
        JSONObject notifyContent = JSON.parseObject(decryptNotify);
        String outTradeNo = notifyContent.getString("out_trade_no");
        String transactionId = notifyContent.getString("transaction_id");
        String refundStatus = notifyContent.getString("refund_status");
        String refundId = notifyContent.getString("refund_id");
        JSONObject amount = notifyContent.getJSONObject("amount");
        if (Objects.nonNull(amount)){
            String amountStr = amount.getString("refund");
            result.setRefundAmount(Long.valueOf(amountStr));
        }
        result.setResult(decryptNotify);
        result.setOrderCode(outTradeNo);
        result.setThirdTransactionCode(transactionId);
        result.setThirdRefundStatus(refundStatus);
        result.setThirdRefundCode(refundId);
        WechatRefundStatusEnum wechatRefundStatusEnum = WechatRefundStatusEnum.valueOf(refundStatus);
        switch (wechatRefundStatusEnum){
            case SUCCESS:
                String successTimeStr = notifyContent.getString("success_time");
                Date successTime = DateUtil.parse(successTimeStr,"yyyy-MM-dd'T'HH:mm:ss");
                result.setRefundStatusEnum(RefundStatusEnum.SUCCESS);
                result.setRefundSuccessTime(successTime);
                break;
            case PROCESSING:
                result.setRefundStatusEnum(RefundStatusEnum.REFUNDING);
                break;
            case ABNORMAL:
            case CLOSED:
                result.setRefundStatusEnum(RefundStatusEnum.FAIL);
                break;
            default: break;
        }
        return result;
    }

    /**
     * 获取支付配置
     * @param merchantCode 商户号
     * @return 支付支付配置
     */
    private WechatPayConfig getWechatPayConfig(String merchantCode) {
        // 获取支付配置
        PayConfig payConfig = PayContext.getPayConfig(PAY_CHANNEL.name(), merchantCode);
        return payConfig.getSceneParams(WechatPayConfig.class);
    }


    private RefundNotifyResult convertSearchRefundOrderResult(JSONObject responseBody) {
        RefundNotifyResult result = new RefundNotifyResult();
        String outTradeNo = responseBody.getString("out_trade_no");
        String transactionId = responseBody.getString("transaction_id");
        String refundId = responseBody.getString("refund_id");
        JSONObject amount = responseBody.getJSONObject("amount");
        if (Objects.nonNull(amount)){
            String amountStr = amount.getString("refund");
            result.setRefundAmount(Long.valueOf(amountStr));
        }
        result.setResult(responseBody.toJSONString());
        result.setOrderCode(outTradeNo);
        result.setThirdTransactionCode(transactionId);
        result.setThirdRefundCode(refundId);
        String refundStatus = responseBody.getString("status");
        result.setThirdRefundStatus(refundStatus);
        WechatRefundStatusEnum wechatRefundStatusEnum = WechatRefundStatusEnum.valueOf(refundStatus);
        switch (wechatRefundStatusEnum){
            case SUCCESS:
                String successTimeStr = responseBody.getString("success_time");
                Date successTime = DateUtil.parse(successTimeStr,"yyyy-MM-dd'T'HH:mm:ss");
                result.setRefundStatusEnum(RefundStatusEnum.SUCCESS);
                result.setRefundSuccessTime(successTime);
                break;
            case PROCESSING:
                result.setRefundStatusEnum(RefundStatusEnum.REFUNDING);
                break;
            case ABNORMAL:
            case CLOSED:
                result.setRefundStatusEnum(RefundStatusEnum.FAIL);
                break;
            default: break;
        }
        return result;
    }

    private PayNotifyResult convertSearchPlaceOrderResult(JSONObject responseBody) {
        PayNotifyResult result = new PayNotifyResult();
        String outTradeNo = responseBody.getString("out_trade_no");
        String transactionId = responseBody.getString("transaction_id");
        result.setResult(responseBody.toJSONString());
        result.setOrderCode(outTradeNo);
        JSONObject amount = responseBody.getJSONObject("amount");
        if (Objects.nonNull(amount)){
            String amountStr = amount.getString("total");
            result.setPayAmount(Long.valueOf(amountStr));
        }
        JSONObject payer = responseBody.getJSONObject("payer");
        if (Objects.nonNull(payer)){
            String openId = payer.getString("sp_openid");
            result.setThirdPayerCode(openId);
        }
        result.setThirdTransactionCode(transactionId);
        String tradeState = responseBody.getString("trade_state");
        result.setThirdTransactionStatus(tradeState);
        if (WechatTradeStatusEnum.SUCCESS.name().equals(tradeState)){
            String successTimeStr = responseBody.getString("success_time");
            Date successTime = DateUtil.parse(successTimeStr,"yyyy-MM-dd'T'HH:mm:ss");
            result.setSuccess(true);
            result.setPaySuccessTime(successTime);
        }else {
            result.setSuccess(false);
        }
        return result;
    }


}
